package com.example.testshapeabletextview;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;

import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.Dimension;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatButton;

import com.google.android.material.resources.MaterialResources;
import com.google.android.material.shape.CornerSize;
import com.google.android.material.shape.CornerTreatment;
import com.google.android.material.shape.EdgeTreatment;
import com.google.android.material.shape.RoundedCornerTreatment;
import com.google.android.material.shape.ShapeAppearanceModel;
import com.google.android.material.shape.ShapeAppearancePathProvider;
import com.google.android.material.shape.Shapeable;

import org.jetbrains.annotations.NotNull;

import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;

public class ShaperableButton extends AppCompatButton implements Shapeable {
    private static final int DEF_STYLE_RES = R.style.Widget_MaterialComponents_ShapeableTextView;

    private final ShapeAppearancePathProvider pathProvider = new ShapeAppearancePathProvider();
    private final RectF destination;
    private final RectF maskRect;
    private final Paint borderPaint;
    private final Paint clearPaint;
    private final Path path = new Path();

    private ColorStateList strokeColor;
    private ShapeAppearanceModel shapeAppearanceModel;
    @Dimension
    private float strokeWidth;
    private Path maskPath;

    public ShaperableButton(@NonNull Context context) {
        this(context, null, 0);
    }

    public ShaperableButton(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ShaperableButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(wrap(context, attrs, defStyleAttr, DEF_STYLE_RES), attrs, defStyleAttr);

        context = getContext();

        clearPaint = new Paint();
        clearPaint.setAntiAlias(true);
        clearPaint.setColor(Color.WHITE);
        clearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
        destination = new RectF();
        maskRect = new RectF();
        maskPath = new Path();

        TypedArray attributes =
                context.obtainStyledAttributes(
                        attrs, R.styleable.ShapeableImageView, defStyleAttr, DEF_STYLE_RES);

        strokeColor = MaterialResources.getColorStateList(
                        context, attributes, R.styleable.ShapeableButton_strokeColor);
        strokeWidth = attributes.getDimensionPixelSize(R.styleable.ShapeableButton_strokeWidth, 0);

        borderPaint = new Paint();
        borderPaint.setStyle(Paint.Style.STROKE);
        borderPaint.setAntiAlias(true);
        shapeAppearanceModel =
                ShapeAppearanceModel.builder(context, attrs, defStyleAttr, DEF_STYLE_RES).build();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setOutlineProvider(new OutlineProvider());
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        setLayerType(LAYER_TYPE_NONE, null);
        super.onDetachedFromWindow();
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        setLayerType(LAYER_TYPE_HARDWARE, null);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(maskPath, clearPaint);
        drawStroke(canvas);
    }

    @Override
    protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
        super.onSizeChanged(width, height, oldWidth, oldHeight);
        updateShapeMask(width, height);
    }

    @Override
    public void setShapeAppearanceModel(@NonNull @NotNull ShapeAppearanceModel shapeAppearanceModel) {
        this.shapeAppearanceModel = shapeAppearanceModel;
        updateShapeMask(getWidth(), getHeight());
        invalidate();
    }

    @NonNull
    @NotNull
    @Override
    public ShapeAppearanceModel getShapeAppearanceModel() {
        return shapeAppearanceModel;
    }

    private void updateShapeMask(int width, int height) {
        destination.set(
                getPaddingLeft(),
                getPaddingTop(),
                width - getPaddingRight(),
                height - getPaddingBottom());
        pathProvider.calculatePath(shapeAppearanceModel, 1f /*interpolation*/, destination, path);
        // Remove path from rect to draw with clear paint.
        maskPath.rewind();
        maskPath.addPath(path);
        // Do not include padding to clip the background too.
        maskRect.set(0, 0, width, height);
        maskPath.addRect(maskRect, Path.Direction.CCW);
    }

    private void drawStroke(Canvas canvas) {
        if (strokeColor == null) {
            return;
        }

        borderPaint.setStrokeWidth(strokeWidth);
        int colorForState =
                strokeColor.getColorForState(getDrawableState(), strokeColor.getDefaultColor());

        if (strokeWidth > 0 && colorForState != Color.TRANSPARENT) {
            borderPaint.setColor(colorForState);
            canvas.drawPath(path, borderPaint);
        }
    }

    /**
     * Sets the stroke color resource for this ImageView. Both stroke color and stroke width must be
     * set for a stroke to be drawn.
     *
     * @param strokeColorResourceId Color resource to use for the stroke.
     * @attr ref com.google.android.material.R.styleable#ShapeableTextView_strokeColor
     * @see #setStrokeColor(ColorStateList)
     * @see #getStrokeColor()
     */
    public void setStrokeColorResource(@ColorRes int strokeColorResourceId) {
        setStrokeColor(AppCompatResources.getColorStateList(getContext(), strokeColorResourceId));
    }

    /**
     * Returns the stroke color for this ImageView.
     *
     * @attr ref com.google.android.material.R.styleable#ShapeableTextView_strokeColor
     * @see #setStrokeColor(ColorStateList)
     * @see #setStrokeColorResource(int)
     */
    @Nullable
    public ColorStateList getStrokeColor() {
        return strokeColor;
    }

    /**
     * Sets the stroke width for this ImageView. Both stroke color and stroke width must be set for a
     * stroke to be drawn.
     *
     * @param strokeWidth Stroke width for this ImageView.
     * @attr ref com.google.android.material.R.styleable#ShapeableTextView_strokeWidth
     * @see #setStrokeWidthResource(int)
     * @see #getStrokeWidth()
     */
    public void setStrokeWidth(@Dimension float strokeWidth) {
        if (this.strokeWidth != strokeWidth) {
            this.strokeWidth = strokeWidth;
            invalidate();
        }
    }

    /**
     * Sets the stroke width dimension resource for this ImageView. Both stroke color and stroke width
     * must be set for a stroke to be drawn.
     *
     * @param strokeWidthResourceId Stroke width dimension resource for this ImageView.
     * @attr ref com.google.android.material.R.styleable#ShapeableTextView_strokeWidth
     * @see #setStrokeWidth(float)
     * @see #getStrokeWidth()
     */
    public void setStrokeWidthResource(@DimenRes int strokeWidthResourceId) {
        setStrokeWidth(getResources().getDimensionPixelSize(strokeWidthResourceId));
    }


    /**
     * Gets the stroke width for this ImageView.
     *
     * @return Stroke width for this ImageView.
     * @attr ref com.google.android.material.R.styleable#ShapeableTextView_strokeWidth
     * @see #setStrokeWidth(float)
     * @see #setStrokeWidthResource(int)
     */
    @Dimension
    public float getStrokeWidth() {
        return strokeWidth;
    }

    public void setStrokeColor(@Nullable ColorStateList strokeColor) {
        this.strokeColor = strokeColor;
        invalidate();
    }


    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    class OutlineProvider extends ViewOutlineProvider {

        private Rect rect = new Rect();

        @Override
        public void getOutline(View view, Outline outline) {
            if (shapeAppearanceModel != null && isRoundRect(shapeAppearanceModel, destination)) {
                destination.round(rect);
                float cornerSize =
                        shapeAppearanceModel.getBottomLeftCornerSize().getCornerSize(destination);
                outline.setRoundRect(rect, cornerSize);
            }
        }

        public boolean isRoundRect(ShapeAppearanceModel shapeAppearanceModel, @NonNull RectF bounds) {
            EdgeTreatment leftEdge = shapeAppearanceModel.getLeftEdge();
            EdgeTreatment rightEdge = shapeAppearanceModel.getRightEdge();
            EdgeTreatment topEdge = shapeAppearanceModel.getTopEdge();
            EdgeTreatment bottomEdge = shapeAppearanceModel.getBottomEdge();
            CornerSize topLeftCornerSize = shapeAppearanceModel.getTopLeftCornerSize();
            CornerSize topRightCornerSize = shapeAppearanceModel.getTopRightCornerSize();
            CornerSize bottomLeftCornerSize = shapeAppearanceModel.getBottomLeftCornerSize();
            CornerSize bottomRightCornerSize = shapeAppearanceModel.getBottomRightCornerSize();
            CornerTreatment topRightCorner = shapeAppearanceModel.getTopRightCorner();
            CornerTreatment topLeftCorner = shapeAppearanceModel.getTopLeftCorner();
            CornerTreatment bottomRightCorner = shapeAppearanceModel.getBottomRightCorner();
            CornerTreatment bottomLeftCorner = shapeAppearanceModel.getBottomLeftCorner();

            boolean hasDefaultEdges =
                    leftEdge.getClass().equals(EdgeTreatment.class)
                            && rightEdge.getClass().equals(EdgeTreatment.class)
                            && topEdge.getClass().equals(EdgeTreatment.class)
                            && bottomEdge.getClass().equals(EdgeTreatment.class);

            float cornerSize = topLeftCornerSize.getCornerSize(bounds);

            boolean cornersHaveSameSize =
                    topRightCornerSize.getCornerSize(bounds) == cornerSize
                            && bottomLeftCornerSize.getCornerSize(bounds) == cornerSize
                            && bottomRightCornerSize.getCornerSize(bounds) == cornerSize;

            boolean hasRoundedCorners =
                    topRightCorner instanceof RoundedCornerTreatment
                            && topLeftCorner instanceof RoundedCornerTreatment
                            && bottomRightCorner instanceof RoundedCornerTreatment
                            && bottomLeftCorner instanceof RoundedCornerTreatment;

            return hasDefaultEdges && cornersHaveSameSize && hasRoundedCorners;
        }
    }
}
